#include "stdafx.h"
#include "PyObjectBase.h"
PyObject* BaseExceptionFreeCADError = 0;
// Constructor
PyObjectBase::PyObjectBase( void* p, PyTypeObject* T )
    : _pcTwinPointer( p ), parent( 0 ), attribute( 0 )
{
    this->ob_type = T;
    _Py_NewReference( this );
//#ifdef FC_LOGPYOBJECTS
//    Base::Console().Log("PyO+: %s (%p)\n",T->tp_name, this);
//#endif
    StatusBits.set( 0 ); // valid, the second bit is NOT set, i.e. it's mutable
}
/// destructor
PyObjectBase::~PyObjectBase()
{
//#ifdef FC_LOGPYOBJECTS
//    Base::Console().Log("PyO-: %s (%p)\n",this->ob_type->tp_name, this);
//#endif
    if ( this->parent )
        this->parent->DecRef();
    if ( this->attribute )
        free( this->attribute ); /* it's a strdup */
}
/*------------------------------
 * PyObjectBase Type		-- Every class, even the abstract one should have a Type
------------------------------*/
/** \brief
 * To prevent subclasses of PyTypeObject to be subclassed in Python we should remove
 * the Py_TPFLAGS_BASETYPE flag. For example, the classes App::VectorPy and App::MatrixPy
 * have removed this flag and its Python proxies App.Vector and App.Matrix cannot be subclassed.
 * In case we want to allow to derive from subclasses of PyTypeObject in Python
 * we must either reimplment tp_new, tp_dealloc, tp_getattr, tp_setattr, tp_repr or set them to
 * 0 and define tp_base as 0.
 */
PyTypeObject PyObjectBase::Type =
{
    PyObject_HEAD_INIT( &PyType_Type )
    0,                                                      /*ob_size*/
    "PyObjectBase",                                         /*tp_name*/
    sizeof( PyObjectBase ),                                 /*tp_basicsize*/
    0,                                                      /*tp_itemsize*/
    /* --- methods ---------------------------------------------- */
    PyDestructor,                                           /*tp_dealloc*/
    0,                                                      /*tp_print*/
    __getattr,                                              /*tp_getattr*/
    __setattr,                                              /*tp_setattr*/
    0,                                                      /*tp_compare*/
    __repr,                                                 /*tp_repr*/
    0,                                                      /*tp_as_number*/
    0,                                                      /*tp_as_sequence*/
    0,                                                      /*tp_as_mapping*/
    0,                                                      /*tp_hash*/
    0,                                                      /*tp_call */
    0,                                                      /*tp_str  */
    0,                                                      /*tp_getattro*/
    0,                                                      /*tp_setattro*/
    /* --- Functions to access object as input/output buffer ---------*/
    0,                                                      /* tp_as_buffer */
    /* --- Flags to define presence of optional/expanded features */
    Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_CLASS,            /*tp_flags */
    "The most base class for Python binding",               /*tp_doc */
    0,                                                      /*tp_traverse */
    0,                                                      /*tp_clear */
    0,                                                      /*tp_richcompare */
    0,                                                      /*tp_weaklistoffset */
    0,                                                      /*tp_iter */
    0,                                                      /*tp_iternext */
    0,                                                      /*tp_methods */
    0,                                                      /*tp_members */
    0,                                                      /*tp_getset */
    0,                                                      /*tp_base */
    0,                                                      /*tp_dict */
    0,                                                      /*tp_descr_get */
    0,                                                      /*tp_descr_set */
    0,                                                      /*tp_dictoffset */
    0,                                                      /*tp_init */
    0,                                                      /*tp_alloc */
    0,                                                      /*tp_new */
    0,                                                      /*tp_free   Low-level free-memory routine */
    0,                                                      /*tp_is_gc  For PyObject_IS_GC */
    0,                                                      /*tp_bases */
    0,                                                      /*tp_mro    method resolution order */
    0,                                                      /*tp_cache */
    0,                                                      /*tp_subclasses */
    0,                                                      /*tp_weaklist */
    0                                                       /*tp_del */
};
/*------------------------------
 * PyObjectBase Methods 	-- Every class, even the abstract one should have a Methods
------------------------------*/
PyMethodDef PyObjectBase::Methods[] =
{
    {NULL, NULL, 0, NULL}        /* Sentinel */
};
PyObject* PyObjectBase::__getattr( PyObject* obj, char* attr )
{
    // This should be the entry in Type
    PyObjectBase* pyObj = static_cast<PyObjectBase*>( obj );
    if ( !pyObj->isValid() )
    {
        PyErr_Format( PyExc_ReferenceError, "Cannot access attribute '%s' of deleted object", attr );
        return NULL;
    }
    PyObject* value = pyObj->_getattr( attr );
#if 1
    if ( value && PyObject_TypeCheck( value, &( PyObjectBase::Type ) ) )
    {
        if ( !static_cast<PyObjectBase*>( value )->isConst() )
            static_cast<PyObjectBase*>( value )->setAttributeOf( attr, pyObj );
    }
#endif
    return value;
}
int PyObjectBase::__setattr( PyObject* obj, char* attr, PyObject* value )
{
    //FIXME: In general we don't allow to delete attributes (i.e. value=0). However, if we want to allow
    //we must check then in _setattr() of all subclasses whether value is 0.
    if ( value == 0 )
    {
        PyErr_Format( PyExc_AttributeError, "Cannot delete attribute: '%s'", attr );
        return -1;
    }
    else if ( !static_cast<PyObjectBase*>( obj )->isValid() )
    {
        PyErr_Format( PyExc_ReferenceError, "Cannot access attribute '%s' of deleted object", attr );
        return -1;
    }
    int ret = static_cast<PyObjectBase*>( obj )->_setattr( attr, value );
#if 1
    if ( ret == 0 )
    {
        static_cast<PyObjectBase*>( obj )->startNotify();
    }
#endif
    return ret;
}
/*------------------------------
 * PyObjectBase attributes	-- attributes
------------------------------*/
PyObject* PyObjectBase::_getattr( char* attr )
{
    if ( PyStrEq( attr, "__class__" ) )
    {
        // Note: We must return the type object here,
        // so that our own types feel as really Python objects
        Py_INCREF( this->ob_type );
        return ( PyObject* )( this->ob_type );
    }
    else if ( PyStrEq( attr, "__members__" ) )
    {
        // Use __dict__ instead as __members__ is deprecated
        return NULL;
    }
    else if ( PyStrEq( attr, "__dict__" ) )
    {
        // Return the default dict
        PyTypeObject* tp = this->ob_type;
        Py_XINCREF( tp->tp_dict );
        return tp->tp_dict;
    }
    else if ( PyStrEq( attr, "softspace" ) )
    {
        // Internal Python stuff
        return NULL;
    }
    else
    {
        // As fallback solution use Python's default method to get generic attributes
        PyObject* w, *res;
        w = PyString_InternFromString( attr );
        if ( w != NULL )
        {
            res = PyObject_GenericGetAttr( this, w );
            Py_XDECREF( w );
            return res;
        }
        else
        {
            // Throw an exception for unknown attributes
            PyTypeObject* tp = this->ob_type;
            PyErr_Format( PyExc_AttributeError, "%.50s instance has no attribute '%.400s'", tp->tp_name, attr );
            return NULL;
        }
    }
}
int PyObjectBase::_setattr( char* attr, PyObject* value )
{
    if ( PyStrEq( attr, "softspace" ) )
        return -1; // filter out softspace
    PyObject* w;
    // As fallback solution use Python's default method to get generic attributes
    w = PyString_InternFromString( attr ); // new reference
    if ( w != NULL )
    {
        // call methods from tp_getset if defined
        int res = PyObject_GenericSetAttr( this, w, value );
        Py_DECREF( w );
        return res;
    }
    else
    {
        // Throw an exception for unknown attributes
        PyTypeObject* tp = this->ob_type;
        PyErr_Format( PyExc_AttributeError, "%.50s instance has no attribute '%.400s'", tp->tp_name, attr );
        return -1;
    }
}
/*------------------------------
 * PyObjectBase repr    representations
------------------------------*/
PyObject* PyObjectBase::_repr( void )
{
    std::stringstream a;
    a << "<base object at " << _pcTwinPointer << ">";
# ifdef FCDebug
    Console().Log( "PyObjectBase::_repr() not overwritten representation!" );
# endif
    return Py_BuildValue( "s", a.str().c_str() );
}
void PyObjectBase::setAttributeOf( const char* attr, const PyObjectBase* par )
{
    if ( this->parent != par )
    {
        Py_XDECREF( this->parent );
        this->parent = const_cast<PyObjectBase*>( par );
        Py_XINCREF( this->parent );
    }
    if ( this->attribute )
    {
        if ( strcmp( this->attribute, attr ) != 0 )
        {
            free( this->attribute );
#if defined (__GNUC__)
            this->attribute =  strdup( attr );
#else
            this->attribute = _strdup( attr );
#endif
        }
    }
    else
    {
#if defined (__GNUC__)
        this->attribute =  strdup( attr );
#else
        this->attribute = _strdup( attr );
#endif
    }
}
void PyObjectBase::startNotify()
{
    if ( this->attribute && this->parent )
    {
        __setattr( this->parent, this->attribute, this );
        if ( PyErr_Occurred() )
            PyErr_Clear();
    }
}
